package compiler.parser;

import compiler.grammar.NonTerminal;
import compiler.grammar.Production;
import compiler.grammar.Terminal;
import compiler.parser.lalr.LALRState;
import compiler.parser.lalr.LALRUtils.Action;
import compiler.parser.lalr.LALRUtils.Reduce;
import compiler.parser.lalr.LALRUtils.Shift;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class LALRParsingTable
{
    //~ Instance fields --------------------------------------------------------

    private final Map<Integer, LALRState>   lalrStates = new HashMap<Integer, LALRState>(); // used for printing only
    private final Map<Integer, NonTerminal> nonTerminals;
    private final Map<Integer, Production>  productions = new HashMap<Integer, Production>();
    private final Map<Integer, Terminal>    terminals;

    // state No., terminal No. ,
    // element: Action ( Shift( state no. ) or reduce( prod no. ) )
    // or Accept, or Error
    private final Action[][] actionTable;
    private int[][]          gotoTable; // state No., non-terminal No., element: state no

    //~ Constructors -----------------------------------------------------------

    public LALRParsingTable(Action[][] actionTable, int[][] gotoTable, Map<Integer, Terminal> terminals, Map<Integer, NonTerminal> nonTerminals, Collection<LALRState> lalrStates, Production startProd)
    {
        this.actionTable  = actionTable;
        this.gotoTable    = gotoTable;
        this.terminals    = terminals;
        this.nonTerminals = nonTerminals;

        for (LALRState state : lalrStates)
            this.lalrStates.put(state.getId(), state);

        for (NonTerminal nt : nonTerminals.values())
        {
            for (Production p : nt.getProductions())
            {
                productions.put(p.getId(), p);
            }
        }

        productions.put(startProd.getId(), startProd);
    }

    //~ Methods ----------------------------------------------------------------

    public Action[][] getActionTable()
    {
        return actionTable;
    }

    public int getActionTableWidth()
    {
        return actionTable[0].length;
    }

    public int getGoToTableWidth()
    {
        return gotoTable[0].length;
    }

    public int[][] getGotoTable()
    {
        return gotoTable;
    }

    public Map<Integer, NonTerminal> getNonTerminals()
    {
        return nonTerminals;
    }

    public Map<Integer, Production> getProductions()
    {
        return productions;
    }

    public LALRState getState(int stateNo)
    {
        return lalrStates.get(stateNo);
    }

    public Map<Integer, Terminal> getTerminals()
    {
        return terminals;
    }

    @Override
    public String toString()
    {
        StringBuilder str = new StringBuilder();

        boolean       thisGrammarCanAccept = false; // if we have no accepting state, show an error

        for (int state = 0; state < actionTable.length; state++)
        {
            str.append("State " + state + ":\n");
            str.append(lalrStates.get(state).toString() + "\t");

            str.append("\n\tAction table:\n\t");

            for (int terminal = 0; terminal < actionTable[0].length;
                     terminal++)
            {
                if (actionTable[state][terminal] != null) // not error
                {
                    if (actionTable[state][terminal] instanceof Shift)
                    {
                        Shift shift = (Shift)actionTable[state][terminal];
                        str.append((terminals.get(terminal)).getName());
                        str.append(": shift and goto ");
                        str.append(shift.stateNumber);
                    }
                    else if (actionTable[state][terminal] instanceof Reduce)
                    {
                        Reduce reduce = (Reduce)actionTable[state][terminal];

                        // default action is at the last index of the action table
                        str.append((terminal == (actionTable[0].length - 1)) ? "default" : terminals.get(terminal).getName());

                        str.append(": reduce by the production ");
                        str.append((productions.get(reduce.productionId)).toString());

                        if ((productions.get(reduce.productionId)).getName() == null)
                        {
                            str.append(" ( Accept ) ");
                            thisGrammarCanAccept = true;
                        }
                    }

                    str.append("\n\t");
                }
            }

            str.append("\n\tGoto table:\n\t");

            for (int nonTerminal = 0; nonTerminal < gotoTable[0].length;
                     nonTerminal++)
            {
                if (gotoTable[state][nonTerminal] != 0) // not error
                {
                    int gotoState = gotoTable[state][nonTerminal] - 1; // restore
                                                                       // value

                    str.append((nonTerminals.get(nonTerminal)).getName());
                    str.append(": goto ");
                    str.append(gotoState);
                    str.append("\n\t");
                }
            }

            str.append("\n");
        }

        if (!thisGrammarCanAccept)
        {
            System.out.println("ERR: this grammar cannot accept, it will never reduce to the start rule\n");
        }

        return str.toString();
    }
}
